/*
    Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.
 */
package erwiki.api.parser;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import org.apache.log4j.Logger;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.Perl5Matcher;

import erwiki.api.exceptions.ProviderException;
import erwiki.api.rwc.RWC;

/**
 * Link parsing operations.
 */
public class LinkParsingOperations {

	private static final Logger log = Logger.getLogger(LinkParsingOperations.class);

	/**
	 * Этот список содержит все зарегистрированные IANA типы протоколов URI по состоянию на июль 2020
	 * года.
	 * <p>
	 * Wiki распознает их все как внешние ссылки.
	 * <p>
	 * Этот массив сортируется при загрузке класса, поэтому можно просто бросить здесь все, что
	 * требуется, в любом порядке.
	 */
	static final String[] EXTERNAL_LINKS = { "IOR:", "SubEthaEdit:", "about:", "acap:", "addbook:", "afp:", "afs:",
			"aim:", "applescript:", "bcp:", "bk:", "btspp:", "callto:", "castanet:", "cdv:", "chrome:", "chttp:",
			"cid:", "crid:", "data:", "dav:", "daytime:", "device:", "dict:", "dns:", "doi:", "dtn:", "ed2k:", "eid:",
			"enp:", "fax:", "feed:", "file:", "finger:", "freenet:", "ftp:", "go:", "gopher:", "gsiftp:", "gsm-sms:",
			"h323:", "h324:", "hdl:", "hnews:", "http:", "https:", "httpsy:", "iioploc:", "ilu:", "im:", "imap:",
			"info:", "ip:", "ipp:", "irc:", "iris.beep:", "itms:", "jar:", "javascript:", "jdbc:", "klik:", "kn:",
			"lastfm:", "ldap:", "lifn:", "livescript:", "lrq:", "mac:", "magnet:", "mailbox:", "mailserver:", "mailto:",
			"man:", "md5:", "mid:", "mms:", "mocha:", "modem:", "moz-abmdbdirectory:", "msni:", "mtqp:", "mumble:",
			"mupdate:", "myim:", "news:", "nfs:", "nltk:", "nntp:", "oai:", "opaquelocktoken:", "pcast:", "phone:",
			"php:", "pop3:", "pop:", "pres:", "printer:", "prospero:", "pyimp:", "rdar:", "res:", "rtsp:", "rvp:",
			"rwhois:", "rx:", "sdp:", "secondlife:", "service:", "sip:", "sips:", "smb:", "smtp:", "snews:", "snmp:",
			"soap.beep:", "soap.beeps:", "soap.udp:", "svn+ssh:", "svn:", "t120:", "tag:", "tann:", "tcp:", "tel:",
			"telephone:", "telnet:", "tftp:", "thismessage:", "tip:", "tn3270:", "tv:", "txmt:", "uddi:", "urn:",
			"uuid:", "vemmi:", "videotex:", "view-source:", "wais:", "wcap:", "webcal:", "whodp:", "whois++:", "whois:",
			"wpn:", "wtai:", "xeerkat:", "xfire:", "xmlrpc.beep:", "xmlrpc.beeps:", "xmpp:", "ymsgr:", "z39.50r:",
			"z39.50s:" };

	static {
		Arrays.sort(EXTERNAL_LINKS);
	}

	/**
	 * Возвращает <i>true</i>, если ссылка действительно является командой для вставки плагина.</br>
	 * В настоящее время проверяется, начинается ли ссылка с "{INSERT" или просто с "{", но не с "{$".
	 *
	 * @param checkedLink Текст ссылки, то есть содержимое текста между [].
	 * @return <i>true</i>, если проверяемый текст похож на команду для вставки плагина.
	 */
	public boolean isPluginLink(String checkedLink) {
		return checkedLink.startsWith("{INSERT") || (checkedLink.startsWith("{") && !checkedLink.startsWith("{$"));
	}

	/**
	 * Возвращает true, если проверяемая ссылка является ссылкой на метаданные.
	 *
	 * @param checkedLink Текст проверяемой ссылки.
	 * @return true, если заданное представляет собой ссылку на метаданные.
	 */
	public boolean isMetadata(String checkedLink) {
		return checkedLink.startsWith("{SET");
	}

	/**
	 * Возвращает <i>true</i>, если ссылка действительно является командой для вставки переменной.</br>
	 * В настоящее время просто проверяется, начинается ли ссылка с "{$".
	 *
	 * @param checkedLink Текст проверяемой ссылки.
	 * @return <i>true</i>, если заданное представляет собой ссылку на переменную.
	 */
	public boolean isVariableLink(String checkedLink) {
		return checkedLink.startsWith("{$");
	}

	/**
	 * Возвращает <i>true</i>, если эта ссылка представляет собой ссылку InterWiki (в форме wiki:page).
	 *
	 * @param checkedLink Текст проверяемой ссылки.
	 * @return <i>true</i>, если эта ссылка представляет собой ссылку InterWiki, в противном случае
	 *         <i>false</i>.
	 */
	public boolean isInterWikiLink(String checkedLink) {
		return interWikiLinkAt(checkedLink) != -1;
	}

	/**
	 * Возвращает позицию символа ':', разделителя ссылки InterWiki (в форме wiki:page).
	 *
	 * @param checkedLink Текст проверяемой ссылки.
	 * @return позицию символа ':', или -1, если такого символа нет.
	 */
	public int interWikiLinkAt(String checkedLink) {
		return checkedLink.indexOf(':');
	}

	/**
	 * Определяет, является ли заданная ссылка - ссылкой за пределы сайта.</br>
	 * Метод распознает наиболее распространенные протоколы, проверяя, не начинается ли ссылка с них.
	 *
	 * @param checkedLink Текст проверяемой ссылки.
	 * @return <i>true</i>, если это ссылка за пределы этой вики.
	 */
	public boolean isExternalLink(String checkedLink) {
		int idx = Arrays.binarySearch(EXTERNAL_LINKS, checkedLink, new StartingComparator());

		// Требуется проверить здесь еще раз; иначе можно получить совпадение, например, с чем-то вроде "h".
		return idx >= 0 && checkedLink.startsWith(EXTERNAL_LINKS[idx]);
	}

	/**
	 * Сопоставляет данную ссылку со списком шаблонов имен изображений, чтобы определить, следует ли
	 * рассматривать ее как встроенное изображение или нет.
	 *
	 * @param link                Текст ссылки для анализа.
	 * @param inlineImagePatterns Список шаблонов имен изображений.
	 * @return
	 */
	public boolean isImageLink(String link, List<Pattern> inlineImagePatterns) {
		link = link.toLowerCase();
		for (Pattern p : inlineImagePatterns) {
			if (new Perl5Matcher().matches(link, p)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Возвращает <i>true</i>, если страница с указанным именем существует; в противном случае
	 * возвращает <i>false</i>.
	 *
	 * @param page Имя ссылки.
	 * @return <i>true</i>, если имя ссылки существует; в противном случае метод возвращает
	 *         <i>false</i>.
	 */
	public boolean linkExists(String page) {
		if (page == null || page.length() == 0) {
			return false;
		}
		try {
			return RWC.INSTANCE.getEngine().getFinalPageName(page) != null;
		} catch (ProviderException e) {
			log.warn("TranslatorReader got a faulty page name [" + page + "]!", e);
			return false;
		}
	}

	/**
	 * Returns link name, if it exists; otherwise it returns {@code null}.
	 *
	 * @param page link name
	 * @return link name, if it exists; otherwise it returns {@code null}.
	 */
	public String linkIfExists(String page) {
		if (page == null || page.length() == 0) {
			return null;
		}
		try {
			return RWC.INSTANCE.getEngine().getFinalPageName(page);
		} catch (ProviderException e) {
			log.warn("TranslatorReader got a faulty page name [" + page + "]!", e);
			return null;
		}
	}

	/**
	 * Сравнивает две строки, и если одна (большая по длине) начинается с другой, то возвращает 0. В
	 * противном случае, работает как и обычный компаратор для строк.
	 */
	private static class StartingComparator implements Comparator<String> {
		/** {@inheritDoc} */
		@Override
		public int compare(String s1, String s2) {
			if (s1.length() > s2.length() && s1.startsWith(s2) && s2.length() > 1) {
				return 0;
			} else if (s2.startsWith(s1) && s1.length() > 1) {
				return 0;
			}

			return s1.compareTo(s2);
		}

	}

}
